/**
 * 
 */
package it.polimi.cg16.controller;

import it.polimi.cg16.exceptions.InactivePlayerException;
import it.polimi.cg16.model.BlackSheep;
import it.polimi.cg16.model.Card;
import it.polimi.cg16.model.Cell;
import it.polimi.cg16.model.GameBoard;
import it.polimi.cg16.model.Player;
import it.polimi.cg16.model.Region;
import it.polimi.cg16.model.Sheep;
import it.polimi.cg16.model.Shepherd;
import it.polimi.cg16.model.Terrain;
import it.polimi.cg16.model.Wolf;
import it.polimi.cg16.net.Adapter;

import java.util.LinkedList;
import java.util.List;

/**
 * This class implements the controller of the player and contains all the
 * information about the player, offering an interface to comunicate with the
 * outside
 * 
 * @author Michele
 * 
 */
public class PlayerController {

    private int id;
    private Game game;
    private Player player;
    private int chosenShepherd;
    private String username;
    private boolean isActive;
    // Network attributes
    private Adapter adapter;

    /**
     * 
     * @param socket
     */
    public PlayerController(Adapter adapter, int id, String username) {
        this.id = id;
        this.adapter = adapter;
        this.username = username;
        isActive = true;
    }

    /**
     * @return the id
     */
    public int getId() {
        return id;
    }

    /**
     * Send a bye message
     */
    public void bye() {
        adapter.byeMessage();
    }

    /**
     * set the game in which player plays
     * 
     * @param game
     */
    public void setGame(Game game) {
        this.game = game;
    }

    /**
     * 
     * @param the
     *            net adapter to be used
     */
    public void setAdapter(Adapter adapter) {
        this.adapter = adapter;
    }

    /**
     * 
     * @param the
     *            username of the player
     */
    public void setUsername(String username) {
        this.username = username;
    }

    /**
     * 
     * @return the player's username
     */
    public String getUsername() {
        return username;
    }

    /**
     * return the game in which player plays
     * 
     * 
     */
    public Game getGame() {
        return game;
    }

    /**
     * set the physical player to the controller
     * 
     * @param player
     */
    public void setPlayer(Player player) {
        this.player = player;

    }

    /**
     * @return the chosenShepherd
     */
    public int getChosenShepherd() {
        return chosenShepherd;
    }

    /**
     * Allow the player to choose an available move
     * 
     * @param availableMove
     * @return the move chosen
     * @throws InactivePlayerException
     */
    public Move chooseMove(List<Move> availableMoves) throws InactivePlayerException {
        adapter.sendMessage("#chooseMove#");
        // send number of available moves
        adapter.sendInt(availableMoves.size());
        for (int i = 0; i < availableMoves.size(); i++) {
            adapter.sendInt(availableMoves.get(i).getType());
        }
        int num;
        num = adapter.readInt();
        if (num == -1) {
            throw new InactivePlayerException();
        }
        for (Move m : availableMoves) {
            if (m.getType() == num) {
                return m;
            }
        }
        return chooseMove(availableMoves);
    }

    /**
     * 
     * @return the physical player
     */
    public Player getPlayer() {
        return player;
    }

    /**
     * @return true if the player is active, false otherwise
     */
    public boolean isActive() {
        return isActive;
    }

    /**
     * @param isActive
     *            the current status of the player
     */
    public void setActive(boolean isActive) {
        this.isActive = isActive;
    }

    /**
     * 
     * @return the number of player's shepherd
     * @throws InactivePlayerException
     */
    public int chooseShepherd() throws InactivePlayerException {
        adapter.sendMessage("#chooseShepherd#");
        chosenShepherd = 2;
        while (chosenShepherd > 1) {
            chosenShepherd = adapter.readInt();
            if (chosenShepherd == -1) {
                throw new InactivePlayerException();
            }
        }
        return chosenShepherd;
    }

    /**
     * 
     * @return the chosen cell to move the shepherd
     * @throws InactivePlayerException
     */
    public int chooseCell() throws InactivePlayerException {
        int chosenCell = 42;
        while (chosenCell > 41) {
            adapter.sendMessage("#chooseCell#");
            chosenCell = adapter.readInt();
            if (chosenCell == -1) {
                throw new InactivePlayerException();
            }
        }
        return chosenCell;
    }

    /**
     * 
     * @param chosenShepherd
     * @return the sheep to move
     * @throws InactivePlayerException
     */
    public Sheep chooseSheep(int chosenShepherd) throws InactivePlayerException {
        adapter.sendMessage("#chooseSheep#");
        adapter.sendInt(chosenShepherd);
        int idSheep = adapter.readInt();
        if (idSheep == -1) {
            throw new InactivePlayerException();
        }
        for (Region r : game.getGameBoard().getRegion()) {
            for (Sheep s : r.getSheep()) {
                if (s.getId() == idSheep) {
                    return s;
                }
            }
        }
        return null;
    }

    /**
     * 
     * @param buyableCardss
     * @return the chosen terrain card
     * @throws InactivePlayerException
     */
    public Card chooseCard(List<Card> buyableCards) throws InactivePlayerException {
        adapter.sendMessage("#chooseCard#");
        adapter.sendInt(buyableCards.size());
        for (Card c : buyableCards) {
            adapter.sendInt(c.getTerrain().getTypeConstant());
            adapter.sendInt(c.getCost());
        }
        int terrainConstant = adapter.readInt();
        if (terrainConstant == -1) {
            throw new InactivePlayerException();
        }
        for (Card card : buyableCards) {
            if (terrainConstant == card.getTerrain().getTypeConstant()) {
                return card;
            }
        }

        return null;
    }

    /**
     * This method manages the update to the client
     */
    public void update() {
        GameBoard gameBoard = game.getGameBoard();
        Cell[] cells = gameBoard.getCell();
        Region[] regions = gameBoard.getRegion();
        BlackSheep blackSheep = gameBoard.getBlackSheep();
        List<Card> deck = gameBoard.getDeck();
        int money = player.getMoney();
        List<Card> terrainCards = player.getTerrainCards();
        List<Shepherd> shepherds = player.getShepherd();
        Shepherd[] allShepherds = game.getGameBoard().getShepherds();
        List<Shepherd> otherShepherds = new LinkedList<>();
        for (int i = 0; i < allShepherds.length; i++) {
            otherShepherds.add(allShepherds[i]);
        }
        for (Shepherd myS : shepherds) {
            otherShepherds.remove(myS);
        }
        Wolf wolf = gameBoard.getWolf();
        adapter.sendMessage("#update#");
        sendCell(cells);
        sendRegions(regions);
        sendBlackSheep(blackSheep);
        sendDeck(deck);
        sendMoney(money);
        sendTerrainCards(terrainCards);
        sendShepherds(shepherds);
        sendOtherShepherds(otherShepherds);
        sendWolf(wolf);
    }

    /**
     * This method manages the initial update to the client
     */
    public void initialUpdate() {
        GameBoard gameBoard = game.getGameBoard();
        Cell[] cells = gameBoard.getCell();
        Region[] regions = gameBoard.getRegion();
        List<Card> deck = gameBoard.getDeck();
        int money = player.getMoney();
        List<Card> terrainCards = player.getTerrainCards();
        Wolf wolf = gameBoard.getWolf();
        adapter.sendMessage("#initialUpdate#");
        sendCell(cells);
        sendRegions(regions);
        sendDeck(deck);
        sendMoney(money);
        sendTerrainCards(terrainCards);
        sendWolf(wolf);

    }

    /**
     * Send cells to the client
     * 
     * @param cells
     */
    private void sendCell(Cell[] cells) {
        adapter.sendMessage("#updateCells#");
        adapter.sendInt(cells.length);
        for (Cell c : cells) {
            adapter.sendInt(c.getId());
            adapter.sendInt(c.getState());
        }
    }

    /**
     * Send regions to the client
     * 
     * @param regions
     */
    private void sendRegions(Region[] regions) {
        adapter.sendMessage("#updateRegions#");
        adapter.sendInt(regions.length);
        for (Region r : regions) {
            adapter.sendInt(r.getId());
            adapter.sendInt(r.getSheep().size());
            for (Sheep s : r.getSheep()) {
                adapter.sendInt(s.getId());
            }
        }

    }

    /**
     * Sends blacksheep to client
     * 
     * @param blackSheep
     */
    private void sendBlackSheep(BlackSheep blackSheep) {
        adapter.sendMessage("#updateBlackSheep#");
        int idRegion = blackSheep.getRegion().getId();
        boolean killed = blackSheep.isKilled();
        adapter.sendInt(idRegion);
        if (killed) {
            adapter.sendInt(1);
        } else {
            adapter.sendInt(0);
        }
    }

    /**
     * Sends wolf to client
     * 
     * @param wolf
     */
    private void sendWolf(Wolf wolf) {
        adapter.sendMessage("#updateWolf#");
        int idRegion = wolf.getRegion().getId();
        adapter.sendInt(idRegion);

    }

    /**
     * Send deck to the client
     * 
     * @param deck
     */
    private void sendDeck(List<Card> deck) {
        adapter.sendMessage("#updateDeck#");
        adapter.sendInt(deck.size());
        for (Card c : deck) {
            adapter.sendInt(c.getTerrain().getTypeConstant());
            adapter.sendInt(c.getCost());
        }

    }

    /**
     * Send money to the client
     * 
     * @param money
     */
    private void sendMoney(int money) {
        adapter.sendMessage("#updateMoney#");
        adapter.sendInt(money);

    }

    /**
     * Send terrain cards to the client
     * 
     * @param terrainCards
     */
    private void sendTerrainCards(List<Card> terrainCards) {
        adapter.sendMessage("#updateTerrainCards#");
        adapter.sendInt(terrainCards.size());
        for (Card c : terrainCards) {
            adapter.sendInt(c.getTerrain().getTypeConstant());
            adapter.sendInt(c.getCost());
        }

    }

    /**
     * Send shepherds to the client
     * 
     * @param shepherds
     */
    private void sendShepherds(List<Shepherd> shepherds) {
        adapter.sendMessage("#updateShepherd#");
        adapter.sendInt(shepherds.size());
        for (Shepherd s : shepherds) {
            adapter.sendInt(s.getCell().getId());
            adapter.sendInt(s.getColor().getId());
        }
        adapter.sendInt(chosenShepherd);

    }

    /**
     * 
     * @param otherShepherds
     */
    private void sendOtherShepherds(List<Shepherd> otherShepherds) {
        adapter.sendMessage("#updateOtherShepherd#");
        adapter.sendInt(otherShepherds.size());
        for (Shepherd s : otherShepherds) {
            adapter.sendInt(s.getCell().getId());
            adapter.sendInt(s.getColor().getId());

        }

    }

    /**
     * Send an end turn notification
     */
    public void endTurn() {
        adapter.sendMessage("#endTurn#");
    }

    /**
     * Terminate the game and close communications
     */
    public void endGame() {
        adapter.sendMessage("#endGame#");
        adapter.sendInt(game.getPlayerController().size() - 1);
        for (PlayerController p : game.getPlayerController()) {
            if (!p.getPlayer().equals(player)) {
                adapter.sendMessage(p.getUsername());
                adapter.sendInt(p.getPlayer().getMoney());
                adapter.sendInt(Terrain.values().length);
                for (Terrain t : Terrain.values()) {
                    adapter.sendInt(t.getTypeConstant());
                    adapter.sendInt(p.getPlayer().getNumberOfCards(t));
                }
                adapter.sendInt(p.getPlayer().getShepherd().get(0).getColor().getId());

            }
        }
    }
}
